package ch.hearc.detectionpresence.gui;

import static com.googlecode.javacpp.Loader.sizeof;
import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_highgui.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Box;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.googlecode.javacv.cpp.opencv_core.CvSeq;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

public class MainWindow extends JFrame {

	public static final String PATH_IMG = "./img";
	public static final int MAX_SLIDER_HEYWOOD = 10;
	public static final int MIN_SLIDER_HEYWOOD = 0;
	public static final int MAX_SLIDER_COLOR = 3*255;
	public static final int MIN_SLIDER_COLOR = 0;
	
	private int sourceObjectsCount = 0;

	private static final long serialVersionUID = -5655669434771620480L;

	private JLabel sourceLabel;
	private JLabel destinationLabel;
	private JLabel detectedItemsLabel;
	private JLabel sourceImageLabel;
	private JLabel destinationImageLabel;

	private JTextField sourceImageField;
	private JTextField destinationImageField;

	private JButton sourceBrowseButton;
	private JButton destinationBrowseButton;
	private JButton detectButton;

	private JList detectedItemsList;
	private DefaultListModel listModel;

	private List<CvSeq> contoursSource;
	private List<CvSeq> contoursDestination;

	private IplImage sourceImage;
	private IplImage destinationImage;

	private double colorThreshold = 20;
	private double heywoodThreshold = 1;
	
	private JLabel colorThresholdLabel;
	private JSlider colorThresholdSlider;
	
	private JLabel heywoordThresholdLabel;
	private JSlider heywoordThresholdSlider;
	
	private Hashtable<Integer, JLabel> colorHashtable = new Hashtable<Integer, JLabel>();
	private Hashtable<Integer, JLabel> heywoodHashtable = new Hashtable<Integer, JLabel>();

	Dimension dim = new Dimension(400, 400);
	private int binaryThreshold;

	public MainWindow() {
		this.contoursSource = new LinkedList<CvSeq>();
		this.contoursDestination = new LinkedList<CvSeq>();
		this.binaryThreshold = 128;
		createComponents();
		addComponents();
		addListeners();
		setProperties();
	}

	private void addListeners() {
		this.sourceBrowseButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				File file = new File(MainWindow.PATH_IMG);
				JFileChooser fileChooser = new JFileChooser(file);
				fileChooser.showOpenDialog(MainWindow.this);
				String path = fileChooser.getSelectedFile().getAbsolutePath();

				sourceImageField.setText(path);
				loadImage(path, sourceImageLabel, true);
				detectObjectsInSourceImage(true);
			}
		});

		this.destinationBrowseButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				File file = new File(PATH_IMG);
				JFileChooser fileChooser = new JFileChooser(file);
				fileChooser.showOpenDialog(MainWindow.this);
				String path = fileChooser.getSelectedFile().getAbsolutePath();

				sourceImageField.setText(path);
				loadImage(path, destinationImageLabel, false);
				detectObjectsInSourceImage(false);
			}
		});

		this.detectedItemsList.addMouseListener(new MouseAdapter() {

			@Override
			public void mousePressed(MouseEvent e) {
				int objectIndex = detectedItemsList.locationToIndex(e.getPoint());

				// dessine les contours de l'objet selectionné dans la source
				CvSize sourceImageSize = sourceImage.asCvMat().cvSize();
				IplImage image = IplImage.create(sourceImageSize, IPL_DEPTH_8U, 3);
				// image.copyFrom(sourceImage.getBufferedImage());
				cvCopy(sourceImage, image);

				cvDrawContours(image, contoursSource.get(objectIndex), CvScalar.WHITE, CvScalar.BLUE, -1, 20, CV_AA);

				// affiche l'image dans la source
				displayImage(image, sourceImageLabel);
			}
		});

		this.detectButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				int index = detectedItemsList.getSelectedIndex();
				if (index != -1) {
					CvSeq selectedObjectContour = contoursSource.get(index);
					CvScalar selectedObjectAverageColor = findAverageColor(selectedObjectContour, sourceImage);
					double selectedObjectHeywoodFactor = heywoodFactor(selectedObjectContour);

					CvSeq bestContour = null;
					double bestColorDifference = Integer.MAX_VALUE;
					double bestHeywoodFactorDifference = Double.MAX_VALUE;

					for (CvSeq contour : contoursDestination) {
						CvScalar scalarToCompare = findAverageColor(contour, destinationImage);
						double colorDifference = scalarDifference(scalarToCompare, selectedObjectAverageColor);
						double heywoodFactorDifference = Math.abs(heywoodFactor(contour) - selectedObjectHeywoodFactor);

						if (colorDifference < colorThreshold && colorDifference < bestColorDifference
								&& heywoodFactorDifference < bestHeywoodFactorDifference && heywoodFactorDifference < heywoodThreshold) {
							bestContour = contour;
							bestColorDifference = colorDifference;
							bestHeywoodFactorDifference = heywoodFactorDifference;
						}
					}

					if (bestContour != null) {
						// dessine les contours de l'objet selectionné dans la
						// source
						CvSize sourceImageSize = destinationImage.asCvMat().cvSize();
						IplImage image = IplImage.create(sourceImageSize, IPL_DEPTH_8U, 3);
						// image.copyFrom(sourceImage.getBufferedImage());
						cvCopy(destinationImage, image);

						cvDrawContours(image, bestContour, CvScalar.WHITE, CvScalar.BLUE, -1, 20, CV_AA);

						// affiche l'image dans la source
						displayImage(image, destinationImageLabel);
					} else {
						JOptionPane.showMessageDialog(null, "Object not found");
					}
				} else {
					JOptionPane.showMessageDialog(null, "Please select an object to detect");
				}
			}
		});
		
		this.colorThresholdSlider.addChangeListener(new ChangeListener()
			{
				
				@Override
				public void stateChanged(ChangeEvent arg0)
					{
					colorThreshold = colorThresholdSlider.getValue();
					updateColorThresholdLabel(colorThreshold);
					}
			});
		
		this.heywoordThresholdSlider.addChangeListener(new ChangeListener()
			{	
				@Override
				public void stateChanged(ChangeEvent arg0)
					{
					heywoodThreshold = heywoordThresholdSlider.getValue() / 10.0;
					updateHeywoodThresholdLabel(heywoodThreshold);
					}
			});
	}

	protected CvScalar findAverageColor(CvSeq objectContour, IplImage image) {
		CvSize imageSize = image.asCvMat().cvSize();

		IplImage mask = IplImage.create(imageSize, IPL_DEPTH_8U, 1);
		cvFillConvexPoly(mask, new CvPoint(cvGetSeqElem(objectContour, 0)), objectContour.total(), CvScalar.WHITE, 0, 0);

		return cvAvg(image, mask);
	}

	protected double scalarDifference(CvScalar s1, CvScalar s2) {
		return (Math.abs(s1.blue() - s2.blue()) + Math.abs(s1.green() - s2.green()) + Math.abs(s1.red() - s2.red()));
	}

	private double heywoodFactor(CvSeq contour) {
		double area = cvContourArea(contour, CV_WHOLE_SEQ, 0);
		double perimeter = cvContourPerimeter(contour);

		return perimeter * perimeter / (4 * Math.PI * area);
	}

	private void displayImage(IplImage image, JLabel destinationLabel) {
		ImageIcon iconSourceImageCopy = new ImageIcon();
		Image scaledSourceImageCopy = image.getBufferedImage().getScaledInstance((int) dim.getWidth(), (int) dim.getHeight(), 0);
		iconSourceImageCopy.setImage(scaledSourceImageCopy);
		destinationLabel.setIcon(iconSourceImageCopy);
	}

	protected void drawRectangle(int objectIndex) {
		JOptionPane.showMessageDialog(null, "clicked item " + objectIndex);
	}

	protected void loadImage(String path, JLabel label, boolean isSource) {
		IplImage image = cvLoadImage(path);

		if (isSource) {
			this.sourceImage = image;
		} else {
			this.destinationImage = image;
		}

		BufferedImage bufferedImageSource = image.getBufferedImage();
		Image scaledImageSource = bufferedImageSource.getScaledInstance((int) dim.getWidth(), (int) dim.getHeight(), 0);

		ImageIcon iconImageSource = new ImageIcon();
		iconImageSource.setImage(scaledImageSource);
		label.setIcon(iconImageSource);
	}

	private void createComponents() {
		this.sourceLabel = new JLabel("Src Image: ");
		this.destinationLabel = new JLabel("Dest Image: ");
		this.detectedItemsLabel = new JLabel("Detected items");

		this.sourceImageLabel = new JLabel();
		this.sourceImageLabel.setMinimumSize(dim);
		this.sourceImageLabel.setMaximumSize(dim);
		this.sourceImageLabel.setPreferredSize(dim);
		this.destinationImageLabel = new JLabel();
		this.destinationImageLabel.setMinimumSize(dim);
		this.destinationImageLabel.setMaximumSize(dim);
		this.destinationImageLabel.setPreferredSize(dim);

		this.sourceImageField = new JTextField();
		this.destinationImageField = new JTextField();

		this.sourceBrowseButton = new JButton("Browse...");
		this.destinationBrowseButton = new JButton("Browse...");
		this.detectButton = new JButton("Detect");

		this.detectedItemsList = new JList();
		this.listModel = new DefaultListModel();
		this.detectedItemsList.setModel(this.listModel);
		
		this.colorThresholdSlider = new JSlider(MIN_SLIDER_COLOR, MAX_SLIDER_COLOR);
		double defaultColorThresholdValue = 20.0;
		this.colorThresholdSlider.setValue((int)defaultColorThresholdValue);
		this.colorThresholdSlider.setPaintTicks(true);
		this.colorThresholdSlider.setPaintLabels(true);
		
		for(int i = MIN_SLIDER_COLOR; i <= MAX_SLIDER_COLOR; i+=128)
			{
			colorHashtable.put(new Integer(i), new JLabel(""+i));
			}
		
		this.colorThresholdSlider.setLabelTable(colorHashtable);
		
		this.heywoordThresholdSlider = new JSlider(MIN_SLIDER_HEYWOOD, 10*MAX_SLIDER_HEYWOOD);
		
		double defaultHeywoodThresholdValue = 1.0;
		this.heywoordThresholdSlider.setValue((int)defaultHeywoodThresholdValue*10);
		this.heywoordThresholdSlider.setPaintTicks(true);
		this.heywoordThresholdSlider.setPaintLabels(true);
		
		for(int i = MIN_SLIDER_HEYWOOD; i <= 10*MAX_SLIDER_HEYWOOD; i+=10)
			{
			heywoodHashtable.put(new Integer(i), new JLabel(""+i/10.0));
			}
		
		this.heywoordThresholdSlider.setLabelTable(heywoodHashtable);
		this.heywoordThresholdSlider.setSnapToTicks(false);
		this.heywoordThresholdSlider.setMajorTickSpacing(10);
		
		this.colorThresholdLabel = new JLabel();
		this.heywoordThresholdLabel = new JLabel();
		updateColorThresholdLabel(defaultColorThresholdValue);
		updateHeywoodThresholdLabel(defaultHeywoodThresholdValue);
	}

	private void updateHeywoodThresholdLabel(double value)
		{
		this.heywoordThresholdLabel.setText("Heywood factor threshold : "+value);
		}

	private void updateColorThresholdLabel(double value)
		{
		this.colorThresholdLabel.setText("Color threshold : "+(int)value);	
		}

	private void addComponents() {
		this.setLayout(new BorderLayout());

		// upper left field
		Box northBox = Box.createHorizontalBox();
		northBox.add(sourceLabel);
		northBox.add(sourceImageField);
		northBox.add(sourceBrowseButton);

		northBox.add(Box.createHorizontalStrut(200));

		northBox.add(destinationLabel);
		northBox.add(destinationImageField);
		northBox.add(destinationBrowseButton);

		this.add(northBox, BorderLayout.NORTH);

		// image 1
		this.add(this.sourceImageLabel, BorderLayout.WEST);

		Box vbox = Box.createVerticalBox();
		vbox.add(detectedItemsLabel);
		vbox.add(detectedItemsList);
		vbox.add(this.detectButton);
		this.add(vbox, BorderLayout.CENTER);

		// image 2
		this.add(this.destinationImageLabel, BorderLayout.EAST);
		
		Box sliderBox = Box.createHorizontalBox();
		sliderBox.add(colorThresholdLabel);
		sliderBox.add(colorThresholdSlider);
		sliderBox.add(heywoordThresholdLabel);
		sliderBox.add(heywoordThresholdSlider);
		this.add(sliderBox, BorderLayout.SOUTH);
	}

	private void setProperties() {
		this.setTitle("Arc Detection de présence");
		this.setLocation(0, 0);
		this.setSize(1280, 720);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setVisible(true);
	}

	protected void detectObjectsInSourceImage(boolean isSource) {
		// seuillage binaire inverse
		CvSize imageSize;
		if (isSource) {
			imageSize = this.sourceImage.asCvMat().cvSize();
		} else {
			imageSize = this.destinationImage.asCvMat().cvSize();
		}

		IplImage imageGrayscale = IplImage.create(imageSize, IPL_DEPTH_8U, 1);
		IplImage imageSeuilleeInverse = IplImage.create(imageSize, IPL_DEPTH_8U, 1);

		if (isSource) {
			cvCvtColor(this.sourceImage, imageGrayscale, CV_BGR2GRAY);
		} else {
			cvCvtColor(this.destinationImage, imageGrayscale, CV_BGR2GRAY);
		}
	
		cvThreshold(imageGrayscale, imageSeuilleeInverse, binaryThreshold, 255, CV_THRESH_BINARY_INV);

		// operations morphologiques
		IplConvKernel element = cvCreateStructuringElementEx(5, 5, 2, 2, CV_SHAPE_RECT, null);

		cvMorphologyEx(imageSeuilleeInverse, imageSeuilleeInverse, null, element, MORPH_OPEN, 10);
		cvMorphologyEx(imageSeuilleeInverse, imageSeuilleeInverse, null, element, MORPH_CLOSE, 10);

		// detection des objets
		CvMemStorage memStorage = cvCreateMemStorage(0);

		CvSeq contoursInverse = new CvSeq();
		CvSeq ptrInverse = new CvSeq();

		cvFindContours(imageSeuilleeInverse, memStorage, contoursInverse, sizeof(CvContour.class), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE,
				cvPoint(0, 0));

		int count = 0;
		if (isSource) {
			this.contoursSource.clear();
		} else {
			this.contoursDestination.clear();
		}

		for (ptrInverse = contoursInverse; ptrInverse != null; ptrInverse = ptrInverse.h_next()) {
			if (ptrInverse.elem_size() > 0) {
				CvSeq points = cvApproxPoly(ptrInverse, sizeof(CvContour.class), memStorage, CV_POLY_APPROX_DP,
						cvContourPerimeter(ptrInverse) * 0.02, 0);

				cvDrawContours(imageSeuilleeInverse, points, CvScalar.BLUE, CvScalar.BLUE, -1, 1, CV_AA);

				if (isSource) {
					this.contoursSource.add(points);
				} else {
					this.contoursDestination.add(points);
				}
				count++;
			}
		}

		this.sourceObjectsCount = count;

		if (isSource) {
			fillDetectedItemsList();
		} else {
			// displayImage(imageSeuilleeInverse, destinationImageLabel);
		}
	}

	private void fillDetectedItemsList() {
		this.listModel.clear();

		for (int i = 1; i <= this.sourceObjectsCount; i++) {
			this.listModel.addElement("Objet " + i);
		}
	}
}
